#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 1994  Ling Li
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


import gobject, gtk, gtk.gdk
from gobject import *
from gtk import *
from gtk.gdk import *

import logging, os, re, string, xml
from xml.dom import minidom

from coralftp_globals import *
from site_info import *
from utils import *

# Site manager manages site address and individual options, the address and
# options are stored in file USERDATADIR/bookmarks.xml.

# Sites are orgnized in tree structure. For quick connects, there is a folder
# named Quick Connect in site manager to store all of them.

defaultBookmarks = """<?xml version='1.0' encoding='UTF-8'?>
<ftp_bookmarks>
  <folder>
    <folder_name>Quick Connect</folder_name>
    <site>
      <site_name>localhost</site_name>
      <server_addr>localhost</server_addr>
    </site>
  </folder>
  <folder>
    <folder_name>Sites</folder_name>
    <folder>
      <folder_name>Linux</folder_name>
      <site>
        <site_name>ftp.debian.org</site_name>
        <server_addr>ftp.debian.org</server_addr>
      </site>
      <site>
        <site_name>non-us.debian.org</site_name>
        <server_addr>non-us.debian.org</server_addr>
      </site>
      <site>
        <site_name>ftp.linuxforum.net</site_name>
        <server_addr>ftp.linuxforum.net</server_addr>
      </site>
      <site>
        <site_name>debian.cn99.com</site_name>
        <server_addr>debian.cn99.com</server_addr>
      </site>
      <site>
        <site_name>Debian@USTC</site_name>
        <server_addr>debian.ustc.edu.cn</server_addr>
      </site>
    </folder>
  </folder>
</ftp_bookmarks>"""

class SiteManager(GObject):

    __gsignals__ = {
        # after-add-node method is invoked when new folder or new site is
        # created. The new created node is passed as signal param.
        'after-add-node' : (gobject.SIGNAL_RUN_LAST,
                            gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
        # before-delete-node method is invoked when existing folder or site
        # would be deleted. The node being deleted is passed as signal param.
        'before-delete-node' : (gobject.SIGNAL_RUN_LAST,
                                gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
        }
    
    data = SiteFolder()

    def __init__(self):
        self.__gobject_init__()
        self.add_folder('/Quick Connect')
        self.add_folder('/Sites')
        self.load()

    def get_site_by_id(self, id):
        if id == None or id == '': raise ValueError, "id should not be empty"
        folders = [self.data]
        while len(folders) > 0:
            folder = folders.pop()
            for name, item in folder.items():
                if isinstance(item, SiteFolder):
                    folders.append(item)
                else:
                    if item['id'] == id:
                        return item
        return None

    def find_folder_by_path(self, path, create = True):
        """This method is used to find a folder in the tree by the path of
        the node. If create is True and the folder is not exists, it would
        be create, and the existing or new created folder would be return.
        If create is False, value None would be returned indicating the
        absense."""
        
        while path[0] == '/':
            path = path[1:]
        folder = self.data
        for part in string.split(path, '/'):
            if folder.has_key(part):
                folder = folder[part]
                if not isinstance(folder, SiteFolder):
                    raise ValueError, folder
            else:
                if create:
                    folder[part] = SiteFolder(name = part)
                    folder = folder[part]
                else:
                    return None
        return folder

    def get_path(self, node):
        """Get the path of a folder or a site. If the node can't be find,
        return None."""

        def find_in_folder(folder, name = ''):
            for key, value in folder.items():
                if value == node:
                    return '%s/%s' % (name, key)
                if isinstance(value, SiteFolder):
                    r = find_in_folder(value, key)
                    if r != None:
                        return '%s/%s' % (name, r)
            return None

        return find_in_folder(self.data)

    def add_folder(self, path):
        """add_folder is used to add a new folder to the path. The new
        created folder is returned."""
        
        folder = self.find_folder_by_path(path, True)
        self.emit('after-add-node', folder)
        return folder

    def add_site(self, path, site_info):
        """add_site is used to add a existing SiteInfo object to the path."""
        
        folder = self.find_folder_by_path(path, True)
        name = site_info['site_name']
        folder[name] = site_info
        self.emit('after-add-node', site_info)
        return site_info

    def delete_folder(self, path):
        """Delete a folder."""

        pos = path.rfind('/')
        parent_folder = self.find_folder_by_path(path[:pos], False)
        if parent_folder != None:
            name = path[pos+1:]
            if parent_folder.has_key(name):
                folder = parent_folder[name]
                self.emit('before-delete-node', folder)
                parent_folder.pop(name)
        return

    def delete_site(self, path):
        """Delete a site."""

        pos = path.rfind('/')
        folder = self.find_folder_by_path(path[:pos], False)
        if folder != None:
            name = path[pos+1:]
            if folder.has_key(name):
                site = folder[name]
                self.emit('before-delete-node', site)
                folder.pop(name)
        return
        
    def save(self):
        """This method save all the bookmarks into the a XML file."""
        def add_node(folder_node, data, path):
            if path != '':
                new_node = dom.createElement('folder_name')
                pos = path.rfind('/')
                folder_name = path[pos+1:]
                new_node.appendChild(dom.createTextNode(folder_name))
                folder_node.appendChild(new_node)
            
            for key, value in data.items():
                if isinstance(value, SiteFolder):
                    new_node = dom.createElement('folder')
                    folder_node.appendChild(new_node)
                    add_node(new_node, value, path + '/' + key)
                elif isinstance(value, SiteInfo):
                    new_node = dom.createElement('site')
                    folder_node.appendChild(new_node)
                    for attr_name, attr_value in value.items():
                        ele = dom.createElement(attr_name)
                        ele.appendChild(dom.createTextNode(
                            attr_value.__str__()))
                        new_node.appendChild(ele)
            return
        
        config_dir = USERDATADIR
        if not os.path.exists(config_dir):
            os.mkdir(config_dir)
        if not os.path.isdir(config_dir):
            raise IOError, '%s is not a directory' % config_dir
        bookmark_file = config_dir + '/bookmarks.xml'
        impl = minidom.getDOMImplementation()
        dom = impl.createDocument(None, "ftp_bookmarks", None)
        add_node(dom.documentElement, self.data, '')
        f = file(bookmark_file, 'w')
        f.write(dom.toxml(encoding='UTF-8'))
        f.close()
        return
            
    def load(self):
        """This method read all bookmarks from a XML file."""
        def get_text(nodelist):
            rc = ''
            for node in nodelist:
                if node.nodeType == node.TEXT_NODE:
                    rc = rc + node.data
            return rc
        
        config_dir = USERDATADIR
        if not os.path.exists(config_dir):
            os.mkdir(config_dir)
        bookmark_file = os.path.join(config_dir, 'bookmarks.xml')
        if os.path.exists(bookmark_file):
            dom = xml.dom.minidom.parse(bookmark_file)
        else:
            dom = xml.dom.minidom.parseString(defaultBookmarks)
        assert dom.documentElement.tagName == 'ftp_bookmarks'
        nodes = [(dom.documentElement, ''),]
        while len(nodes) > 0:
            (node, path) = nodes.pop(0)
            for child_node in node.getElementsByTagName('folder'):
                if child_node.parentNode != node: continue
                ele = child_node.getElementsByTagName('folder_name')[0]
                child_name = get_text(ele.childNodes)
                nodes.append((child_node, path + '/' + child_name))
            if path != '':
                folder = self.find_folder_by_path(path)
                for child_node in node.getElementsByTagName('site'):
                    if child_node.parentNode != node: continue
                    siteinfo = SiteInfo()
                    siteinfo.from_xml_node(child_node)
                    folder[siteinfo['site_name']] = siteinfo
        return

gobject.type_register(SiteManager)

class SiteManagerDialog:

    COL_NAME = 0
    COL_SITEINFO = 1
    COL_ICON = 2

    # WIDGETS is used to represent relation between data and view.
    # The 1st element is key in the data dictionary. The 2nd element is
    # default value of data. The 3rd element is the name of widget and
    # the last one is the type of the widget.
    DATA_NAME = 0
    DATA_DEFAULT = 1
    WIDGET_NAME = 2
    WIDGET_TYPE = 3
    WIDGETS = (
        ('site_name', '', 'entry_sitename', Entry),
        ('server_addr', '', 'entry_ipaddr', Entry),
        ('server_port', '21', 'spin_port', SpinButton),
        ('anonymous', 'True', 'cb_anonymous', CheckButton),
        ('username', '', 'entry_username', Entry),
        ('password', '', 'entry_password', Entry),
        ('remote_path', '', 'entry_remote_path', Entry),
        ('local_path', '', 'entry_local_path', Entry),
        ('remote_charset', 'GBK', 'entry_remote_charset', Entry),
        ('notes', '', 'tv_notes', TextView),
        ('use_pasv', 'True', 'cb_use_pasv', CheckButton))
    
    actions = None

    # __old_selected is used to save original selected element
    __old_selected = None
    __selected = None
    __node = None
    __parent = None
    __parent_node = None

    def __init__(self, coralftp):

        ACTIONS = {
            'connect' : { 'sensitive' : False,
                          'update' : self.on_connect_action_update,
                          'execute' : self.on_connect_action_execute},
            'apply' : { 'sensitive' : False,
                        'update' : self.on_apply_action_update,
                        'execute' : self.on_apply_action_execute},
            'close' : { 'execute' : self.on_close_action_execute},
            'new_site' : { 'sensitive' : False,
                           'update' : self.on_new_site_action_update,
                           'execute' : self.on_new_site_action_execute},
            'new_group' : { 'sensitive' : False,
                            'update' : self.on_new_group_action_update,
                            'execute' : self.on_new_group_action_execute},
            'delete' : { 'sensitive' : False,
                         'update' : self.on_delete_action_update,
                         'execute' : self.on_delete_action_execute},
            'rename' : { 'sensitive' : False,
                         'update' : self.on_rename_action_update,
                         'execute' : self.on_rename_action_execute},
            }
        
        MENU_ACTIONS = { 'mi_sm_connect' : 'connect',
                         'mi_sm_new_site' : 'new_site',
                         'mi_sm_new_group' : 'new_group',
                         'mi_sm_delete' : 'delete',
                         'mi_sm_rename' : 'rename' }
        
        self.__coralftp = coralftp
        self.__site_manager = coralftp.get_site_manager()
        self.__site_manager.connect('after-add-node',
                                    self.on_site_manager_add_node)
        self.__site_manager.connect('before-delete-node',
                                    self.on_site_manager_delete_node)
        self.__xml = get_glade_xml('site_manager_dialog')
        self.__xml.signal_autoconnect(self)

        # initialize action list
        self.actions = ActionList(ACTIONS)
        self.actions['connect'].attach(
            self.__xml.get_widget('btn_connect'))
        self.actions['close'].attach(
            self.__xml.get_widget('btn_close'))
        self.actions['apply'].attach(
            self.__xml.get_widget('btn_apply'))
        self.actions['new_site'].attach(
            self.__xml.get_widget('btn_new_site'))
        self.actions['new_group'].attach(
            self.__xml.get_widget('btn_new_group'))
        self.actions['delete'].attach(
            self.__xml.get_widget('btn_delete'))
        
        self.__dialog = self.__xml.get_widget('site_manager_dialog')
        self.__tree = self.__xml.get_widget('sites_tree')
        self.__model = TreeStore(TYPE_STRING, TYPE_PYOBJECT, gtk.gdk.Pixbuf)
        self.__tree.set_model(self.__model)
        tree_selection = self.__tree.get_selection()
        tree_selection.set_mode(gtk.SELECTION_BROWSE)
 
        column = TreeViewColumn(_("Name"))
        renderer = CellRendererPixbuf()
        column.pack_start(renderer, expand=False)
        column.add_attribute(renderer, 'pixbuf',SiteManagerDialog.COL_ICON)
        renderer = CellRendererText()
        column.pack_start(renderer, expand=True)
        column.add_attribute(renderer, 'text', SiteManagerDialog.COL_NAME)
        self.__tree.append_column(column)

        # drag & drop
        # drag and drop only happens in the tree widget
##         TARGET_SITEINFO = (("coralftp siteinfo", TARGET_SAME_WIDGET, 80),)
##         self.__tree.drag_source_set(BUTTON1_MASK,
##                                     TARGET_SITEINFO,
##                                     ACTION_COPY)
##         self.__tree.drag_dest_set(DEST_DEFAULT_ALL,
##                                   TARGET_SITEINFO,
##                                   ACTION_COPY)
##         self.__tree.connect("drag-data-get", self.on_drag_data_get)
##         self.__tree.connect("drag-data-received", self.on_drag_data_recv)
##         self.__tree.connect("drag-drop", self.on_drag_drop)
        self.__tree.set_reorderable(True)

        # popup menu
        self.__menu_xml = get_glade_xml('site_manager_menu')
        self.__menu = self.__menu_xml.get_widget('site_manager_menu')
        for widget_name, action_name in MENU_ACTIONS.items():
            widget = self.__menu_xml.get_widget(widget_name)
            self.actions[action_name].attach(widget)
            
        self.__build_tree()
        return

    def run(self):
        resp = None
        while 1:
            resp = self.__dialog.run()
            if resp == RESPONSE_OK:
                break
            elif resp == RESPONSE_CLOSE or resp == RESPONSE_DELETE_EVENT:
                break
        return resp

    def destroy(self):
        for name, action in self.actions.items():
            action.remove()
        self.__dialog.destroy()
        return

    def __get_folder_icon(self):
        return gdk.pixbuf_new_from_file(
            os.path.join(coralftp_datadir, 'folder.png'))

    def __get_site_icon(self):
        return gdk.pixbuf_new_from_file(
            os.path.join(coralftp_datadir, 'computer.png'))

    def __build_tree(self):
        def process(data, start_iter):
            for key, value in data.items():
                if isinstance(value, SiteFolder):
                    icon = self.__get_folder_icon()
                else:
                    icon = self.__get_site_icon()
                child_iter = self.__model.append(start_iter)
                self.__model.set_value(child_iter,
                                       SiteManagerDialog.COL_NAME,
                                       key)
                self.__model.set_value(child_iter,
                                       SiteManagerDialog.COL_SITEINFO,
                                       value)
                self.__model.set_value(child_iter,
                                       SiteManagerDialog.COL_ICON,
                                       icon)
                if isinstance(value, SiteFolder):
                    process(value, child_iter)
                    
        iter = self.__model.get_iter_root()
        process(self.__site_manager.data, iter)
        self.__xml.get_widget('sites_tree').expand_all()
        return

    def __find_iter(self, path):
        # find the folder of the item
        iter = None
        for part in string.split(path[1:], '/'):
            found = False
            if iter == None:
                child = self.__model.get_iter_root()
            else:
                child = self.__model.iter_children(iter)
            while child != None:
                name = self.__model.get_value(
                    child, SiteManagerDialog.COL_NAME)
                if name == part:
                    found = True
                    iter = child
                    break
                child = self.__model.iter_next(child)
            if not found:
                # create a coresponding folder
                parent_folder = self.__model.get_value(
                    iter, SiteManagerDialog.COL_SITEINFO)
                child_folder = parent_folder[part]
                iter = self.__model.append(iter, None)
                icon = self.__get_folder_icon()
                self.__model.set_value(
                    iter, SiteManagerDialog.COL_ICON, icon)
                self.__model.set_value(
                    iter, SiteManagerDialog.COL_NAME, part)
                self.__model.set_value(
                    iter, SiteManagerDialog.COL_SITEINFO, child_folder)
        return iter

    def on_site_manager_add_node(self, obj, node, *args):
        "Add node to TreeView."
        path = self.__site_manager.get_path(node)
        pos = path.rfind('/')
        self.__old_selected = None
        self.__parent = self.__find_iter(path[:pos])
        if isinstance(node, SiteFolder):
            icon = self.__get_folder_icon()
        else:
            icon = self.__get_site_icon()
        row = (path[pos+1:], node, icon)
        self.__selected = self.__model.append(self.__parent, row)
        self.__node = node
        self.__tree.get_selection().select_iter(self.__selected)
        self.__tree.emit('cursor-changed')
        return

    def on_site_manager_delete_node(self, obj, node, *args):
        "Delete node from TreeView."
        path = self.__site_manager.get_path(node)
        iter = self.__find_iter(path)
        parent = self.__model.iter_parent(iter)
        self.__model.remove(iter)
        if parent != None:
            self.__tree.get_selection().select_iter(parent)
            self.__tree.emit('cursor-changed')
        self.__selected = parent
        self.__node = self.__model.get_value(self.__selected,
                                             SiteManagerDialog.COL_SITEINFO)
        self.__parent = self.__model.iter_parent(self.__selected)
        self.__old_selected = None
        return

    def __check_change(self, selected):
        """Check if the data in the panel has been changed."""
        si = self.__model.get_value(selected,
                                    SiteManagerDialog.COL_SITEINFO)
        
        if not isinstance(si, SiteInfo):
            raise ValueError, "The selected should be a site"

        changed = False
        for item in SiteManagerDialog.WIDGETS:
            data_name = item[SiteManagerDialog.DATA_NAME]
            widget_name = item[SiteManagerDialog.WIDGET_NAME]
            widget = self.__xml.get_widget(widget_name)
            if not changed and isinstance(widget, Entry):
                v = widget.get_text()
            if not changed and isinstance(widget, CheckButton):
                v = widget.get_property('active').__str__()
            if not changed and isinstance(widget, TextView):
                buf = widget.get_buffer()
                v = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
            changed = (not si.has_key(data_name)
                       and v != item[SiteManagerDialog.DATA_DEFAULT])
            changed = changed or (si.has_key(data_name) and
                                  v != si[data_name])
            if changed:
                break
        return changed

    def __load_from_data(self, selected):
        attrs = self.__model.get_value(selected,
                                       SiteManagerDialog.COL_SITEINFO)
        active = isinstance(attrs, SiteInfo)
        if active:
            for item in SiteManagerDialog.WIDGETS:
                if not attrs.has_key(item[SiteManagerDialog.DATA_NAME]):
                    item_value = item[SiteManagerDialog.DATA_DEFAULT]
                else:
                    item_value = attrs[item[SiteManagerDialog.DATA_NAME]]
                widget = self.__xml.get_widget(
                    item[SiteManagerDialog.WIDGET_NAME])
                if isinstance(widget, Entry):
                    widget.set_text(item_value)
                elif isinstance(widget, CheckButton):
                    if item_value == 'True':
                        widget.set_property('active', True)
                    else:
                        widget.set_property('active', False)
                elif isinstance(widget, TextView):
                    buf = widget.get_buffer()
                    buf.set_text(item_value)
        return

    def __save_to_data(self, selected):
        si = self.__model.get_value(selected, SiteManagerDialog.COL_SITEINFO)
        
        # if the name of the site has been changed, update the dictionary key
        widget = self.__xml.get_widget('entry_sitename')
        new_name = string.strip(widget.get_text())
        old_name = si['site_name']
        if new_name != old_name:
            parent = self.__model.iter_parent(selected)
            parent_node = self.__model.get_value(
                parent, SiteManagerDialog.COL_SITEINFO)
            del parent_node[old_name]
            parent_node[new_name] = si

        # save all values in widgets
        for item in SiteManagerDialog.WIDGETS:
            widget = self.__xml.get_widget(
                item[SiteManagerDialog.WIDGET_NAME])
            data_name = item[SiteManagerDialog.DATA_NAME]
            if isinstance(widget, Entry):
                if widget.get_visibility():
                    si[data_name] = string.strip(widget.get_text())
                else:
                    si[data_name] = widget.get_text()
            elif isinstance(widget, CheckButton):
                si[data_name] = widget.get_property('active').__str__()
            elif isinstance(widget, TextView):
                buf = widget.get_buffer()
                si[data_name] = buf.get_text(buf.get_start_iter(),
                                             buf.get_end_iter())
        
        self.__site_manager.save()
        return

    def __validate_node_name(self, name, parent, node=None, is_folder=False):
        "Check if the new name of the node is validate."
        error_message = None
        if name == '':
            if is_folder:
                error_message = _("The name of the group should not be empty.")
            else:
                error_message = _("The name of the site should not be empty.")
        if name.find('/') >= 0:
            if is_folder:
                error_message = _("The name of the group should not contain backslash('/').")
            else:
                error_message = _("The name of the site should not contain backslash('/').")
        if parent.has_key(name) and parent[name] != node:
            error_message = _("The site or folder named %s already exists.") % name
        return error_message
    
    def on_btn_local_path_browse_clicked(self, obj, *args):
        """This method is used to choose a local path."""
        dlg = FileSelection(title=_("Local Path"))
        resp = dlg.run()
        if resp == RESPONSE_OK:
            widget = self.__xml.get_widget('entry_local_path')
            widget.set_text(dlg.get_property('filename'))
        dlg.destroy()
        return

    def on_sites_tree_button_press_event(self, obj, event):
        """This method is used to process left mouse click. If current item
        is a site, just connect."""
        
        pass
    
    def on_sites_tree_button_release_event(self, obj, event):
        """This method is used to process right mouse double click."""
        
        if event.button == 3:
            self.__menu.popup(None, None, None, 0, 0)
        return

    def on_close_action_execute(self, obj, *args):
        """Close the window"""
        
        self.__dialog.emit('response', gtk.RESPONSE_CLOSE)
        return
        
    def on_cb_anonymous_toggled(self, obj, *args):
        """This method is used to change username and password inputbox state
        when anonymous checkbox is changed."""
        
        active = obj.get_property('active')
        widget = self.__xml.get_widget('entry_username')
        widget.set_property('sensitive', not active)
        widget = self.__xml.get_widget('entry_password')
        widget.set_property('sensitive', not active)
        return
    
    def on_sites_tree_cursor_changed(self, obj, *args):
        """When user select some site or folder in the sites tree, this
        method is invoked."""

        tree, self.__selected = self.__tree.get_selection().get_selected()
        self.__parent = self.__model.iter_parent(self.__selected)
        self.__node = self.__model.get_value(self.__selected,
                                             SiteManagerDialog.COL_SITEINFO)
        self.__parent_node = self.__model.get_value(
            self.__selected, SiteManagerDialog.COL_SITEINFO)

        # check if the orignal selected node has been saved
        if self.__old_selected != None:
            node = self.__model.get_value(self.__old_selected,
                                          SiteManagerDialog.COL_SITEINFO)
            if isinstance(node, SiteInfo) and self.__check_change(self.__old_selected):
                dlg = MessageDialog(type=MESSAGE_QUESTION,
                                    buttons=BUTTONS_YES_NO,
                                    message_format=_("The site information has not been saved, do you want to save it?"))
                resp = dlg.run()
                dlg.destroy()
                if resp == RESPONSE_YES:
                    # check if the new sitename is validate
                    widget = self.__xml.get_widget('entry_sitename')
                    sitename = string.strip(widget.get_text())
                    node = self.__model.get_value(
                        self.__old_selected, SiteManagerDialog.COL_SITEINFO)
                    parent = self.__model.iter_parent(self.__old_selected)
                    parent_node = self.__model.get_value(
                        parent, SiteManagerDialog.COL_SITEINFO) 
                    error_message = self.__validate_node_name(
                        name=sitename, parent=parent_node, node=node)
                    if error_message != None:
                        dlg = MessageDialog(type=MESSAGE_ERROR,
                                            buttons=BUTTONS_OK,
                                            message_format=error_message)
                        dlg.run()
                        dlg.destroy()
                        # change back to the original selection
                        self.__tree.get_selection().select_iter(
                            self.__old_selected)
                        return

                # save changes into site_manager.data
                self.__save_to_data(self.__old_selected)

        # read new information from site_manager.data
        widget = self.__xml.get_widget('nb_info')
        widget.set_property('sensitive', self.__node != None \
                            and isinstance(self.__node, SiteInfo))

        self.__load_from_data(self.__selected)
        
        self.__old_selected = self.__selected
        return

    def on_apply_action_update(self, obj, *args):
        obj.set_property('sensitive', self.__selected != None \
                         and isinstance(self.__node, SiteInfo) \
                         and self.__check_change(self.__selected))
        return

    def on_apply_action_execute(self, obj, *args):
        """When button apply is clicked, all the site info should be saved.
        Only current site is saved now."""
        
        tree, selected = self.__tree.get_selection().get_selected()
        node = self.__model.get_value(selected,
                                      SiteManagerDialog.COL_SITEINFO)
        parent = self.__model.iter_parent(selected)
        parent_node = self.__model.get_value(parent,
                                             SiteManagerDialog.COL_SITEINFO)

        # make sure sitename is validate
        widget = self.__xml.get_widget('entry_sitename')
        sitename = string.strip(widget.get_text())
        if sitename != node['site_name']:
            error_message = self.__validate_node_name(name=sitename,
                                                      parent=parent_node,
                                                      node=node)
            if error_message != None:
                dlg = MessageDialog(type=MESSAGE_ERROR, buttons=BUTTONS_OK,
                                    message_format=error_message)
                dlg.run()
                dlg.destroy()
                return

        # change site_name in path information
        self.__model.set_value(selected, SiteManagerDialog.COL_NAME,
                               sitename)

        # save changes to site_manager.data
        self.__save_to_data(selected)
        return

    def on_new_site_action_update(self, obj, *args):
        obj.set_property('sensitive', self.__selected != None)
        return

    def on_new_site_action_execute(self, obj, *args):
        """Popup a dialog and add a new site to the current folder."""
        def on_dialog_response(dlg, resp, *args):
            v = utf8_to_unicode(string.strip(dlg.value))
            dlg.destroy()
            if resp == RESPONSE_OK and v != '':
                # Find the folder of the selected node if it's a site.
                tree, selected = self.__tree.get_selection().get_selected()
                node = self.__model.get_value(selected,
                                              SiteManagerDialog.COL_SITEINFO)
                if isinstance(node, SiteFolder):
                    folder = node
                else:
                    folder_node = self.__model.iter_parent(selected)
                    folder = self.__model.get_value(
                        folder_node, SiteManagerDialog.COL_SITEINFO)

                error_message = self.__validate_node_name(v, folder)
                if error_message != None:
                    dlg = MessageDialog(type=MESSAGE_ERROR,
                                        buttons=BUTTONS_OK,
                                        message_format=error_message)
                    dlg.connect('response',
                                lambda obj, resp, *args: obj.destroy())
                    dlg.show()
                    dlg.set_modal(True)
                    return
            
                # Get which folder is selected first, is a site is selected,
                # create a new site as the brother of the site.
                si = SiteInfo()
                si['site_name'] = v
                path = self.__site_manager.get_path(folder)
                self.__site_manager.add_site(path, si);
                self.__site_manager.save()
            return
        
        from general_input import GeneralInputDialog
        dlg = GeneralInputDialog(_("Site name"),
                                 _("Please input name of the site:"), "")
        dlg.connect('response', on_dialog_response)
        dlg.run()
        return

    def on_new_group_action_update(self, obj, *args):
        obj.set_property('sensitive',
                         self.__node != None \
                         and isinstance(self.__node, SiteFolder))
        return

    def on_new_group_action_execute(self, obj, *args):
        """Popup a dialog and add a new folder to the current folder."""
        def on_dialog_response(dlg, resp, *args):
            v = utf8_to_unicode(string.strip(dlg.value))
            dlg.destroy()
            if resp == RESPONSE_OK and v != '':
                tree, selected = self.__tree.get_selection().get_selected()
                node = self.__model.get_value(selected,
                                              SiteManagerDialog.COL_SITEINFO)
                if not isinstance(node, SiteFolder):
                    raise ValueError, 'Selected node should be a folder'
            
                # Check if the name contains backslash
                error_message = self.__validate_node_name(v, node,
                                                          is_folder=True)
                if error_message != None:
                    dlg = MessageDialog(type=MESSAGE_ERROR,
                                        buttons=BUTTONS_OK,
                                        message_format=error_message)
                    dlg.connect('response',
                                lambda obj, resp, *args: obj.destroy())
                    dlg.show()
                    dlg.set_modal(True)
                    return
                
                path = self.__site_manager.get_path(node)
                self.__site_manager.add_folder('%s/%s' % (path, v))
                self.__site_manager.save()
            return

        from general_input import GeneralInputDialog
        dlg = GeneralInputDialog(_("Group name"),
                                 _("Please input name of the group:"), "")
        dlg.connect('response', on_dialog_response)
        resp = dlg.run()
        return

    def on_delete_action_update(self, obj, *args):
        obj.set_property('sensitive', self.__parent != None)
        return

    def on_delete_action_execute(self, obj, *args):
        """Delete a site or folder."""

        tree, selected = self.__tree.get_selection().get_selected()
        node = self.__model.get_value(
            selected, SiteManagerDialog.COL_SITEINFO)
        if isinstance(node, SiteFolder):
            child_num = self.__model.iter_n_children(selected)
            if child_num > 0:
                dlg = MessageDialog(
                    type=MESSAGE_QUESTION, buttons=BUTTONS_YES_NO,
                    message_format=_("Are you sure you wan\'t to delete the group and all its contains?"))
                resp = dlg.run()
                dlg.destroy()
                if resp != RESPONSE_YES:
                    return
            path = self.__site_manager.get_path(node)
            self.__site_manager.delete_folder(path)
            self.__site_manager.save()
        else:
            dlg = MessageDialog(
                type=MESSAGE_QUESTION, buttons=BUTTONS_YES_NO,
                message_format=_("Are you sure you wan\'t to delete the site?"))
            resp = dlg.run()
            dlg.destroy()
            if resp != RESPONSE_YES:
                return
            path = self.__site_manager.get_path(node)
            self.__site_manager.delete_site(path)
            self.__site_manager.save()
        return

    def on_rename_action_update(self, obj, *args):
        obj.set_property('sensitive', self.__node != None \
                         and self.__parent != None \
                         and isinstance(self.__node, SiteFolder))
        return

    def on_rename_action_execute(self, obj, *args):
        "Start to rename a folder or a site."
        def on_dialog_response(dlg, resp, *args):
            new_name = utf8_to_unicode(string.strip(dlg.value))
            dlg.destroy()
            if old_name != new_name:
                error_message = self.__validate_node_name(
                    new_name, parent_node, node=node, is_folder=True)
                if error_message != None:
                    dlg = MessageDialog(type=MESSAGE_ERROR,
                                        buttons=BUTTONS_OK,
                                        message_format=error_message)
                    dlg.connect('response',
                                lambda obj, resp, *args: obj.destroy())
                    dlg.show()
                    dlg.set_modal(True)
                else:
                    node.name
                    del parent_node[old_name]
                    parent_node[new_name] = node
                    self.__model.set_value(
                        selected, SiteManagerDialog.COL_NAME, new_name)
                    self.__site_manager.save()
            return
        
        tree, selected = self.__tree.get_selection().get_selected()
        node = self.__model.get_value(selected,
                                      SiteManagerDialog.COL_SITEINFO)
        if not isinstance(node, SiteFolder): return
        old_name = node.name
        parent = self.__model.iter_parent(selected)
        parent_node = self.__model.get_value(
            parent, SiteManagerDialog.COL_SITEINFO)
        from general_input import GeneralInputDialog
        dlg = GeneralInputDialog(_("Rename group"),
                                 _("Please input new name of the group:"),
                                 old_name)
        dlg.connect('response', on_dialog_response)
        resp = dlg.run()
        return

    def on_connect_action_update(self, obj, *args):
        obj.set_property('sensitive',
                         self.__selected != None \
                         and isinstance(self.__node, SiteInfo))
        return

    def on_connect_action_execute(self, obj, *args):
        self.on_apply_action_execute(self, obj)
        self.__dialog.emit('response', RESPONSE_OK)
        return

    def on_drag_drop(self, *args):
        print 123
 
    def on_drag_data_get(self, widget, context, selection, targetType,
                         eventTime):
        print targetType, eventTime
        if self.__selected != None:
            path = self.__model.get_path(self.__selected)
            selection.tree_set_row_drag_data(self.__model, path)
        return

    def on_drag_data_recv(self, widget, context, x, y, selection,
                          targetType, time):
        model, path = selection.tree_get_row_drag_data()
        return

    def get_selected_site(self):
        return self.__node

# TODO:
# 1. Connect button
# 2. enable/disable button
# 3. more options
# 4. more right click item

